#include "stm32f10x.h"                  // Device header
#include "led.h"
#include "ADC.h"
#include "MyDMA.h"
/**
* @brief  选择单次触发、非扫描模式进行ADC初始化（对于是否进行中断与看门狗的配置，看函数内部注释）。该模式的优势在于读取前要先触发，而在触发时可以自行选择
					通道，实现多通道采集，无需担心数据被覆盖
  * @param  RCC_APB2Periph_ADC: 开启哪个ADC的时钟（详见RCC_APB2PeriphClockCmd函数）
	* @param  RCC_APB2Periph_GPIO: 开启哪个GPIO的时钟（同上）
	* @param  GPIOx:选择哪一片GPIO
	* @param  pin：一片GPIO中具体哪个引脚（参照结构体GPIO_InitTypeDef中的定义）
	* @param  RCC_PCLK2：对APB2的72MHz时钟，选择2、4、6、8分频。但因为STM32的ADC最大频率就是14MHz的，给更大的频率会导致稳定性下降，应该选6、8分频（详见
						RCC_ADCCLKConfig函数）
	* @param  ADCx：选用哪个ADC
	* @param  ADC_Mode：工作模式，分独立模式和各种双ADC模式（参考ADC_mode）
	* @param  ADC_DataAlign：对齐方式。STM32的ADC是12位的，而数据寄存器是16位的，因此需要选择数据是向左还是向右对齐。前者会使得数据变大16倍，需要再进
						行处理（参考ADC_data_align）
	* @param  ADC_ExternalTrigConv：外部触发转换选择，即选择硬件触发的触发控制的触发源（如果选用软件触发，这里就填ADC_ExternalTrigConv_None即可，参考
						ADC_external_trigger_sources_for_regular_channels_conversion）
  * @retval None
  */
void AD_Init_SingleTriggerMode(uint32_t RCC_APB2Periph_ADC, uint32_t RCC_APB2Periph_GPIO, GPIO_TypeDef* GPIOx, uint16_t pin, uint32_t RCC_PCLK2, 
	ADC_TypeDef* ADCx, uint32_t ADC_Mode, uint32_t ADC_DataAlign, uint32_t ADC_ExternalTrigConv)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC, ENABLE);
	gpio_Init(RCC_APB2Periph_GPIO, GPIOx, pin, GPIO_Mode_AIN);
	RCC_ADCCLKConfig(RCC_PCLK2);//配置ADCCLK分频器
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;
	ADC_InitStructure.ADC_NbrOfChannel = 1;//此处为通道数目的选择，默认为1（其实在非扫描模式下序列中只有第一个有效，该成员可以不初始化）
	ADC_Init(ADCx, &ADC_InitStructure);//进行ADC初始化
	/*该部分是中断与看门狗的配置，非必须
	
	*/
	ADC_Cmd(ADCx, ENABLE);//开启ADC电源，以下四个函数进行ADC校准
	ADC_ResetCalibration(ADCx);//复位校准。本质上是软件在ADC_CR2寄存器置1，即SET，于是开始复位
	while(ADC_GetResetCalibrationStatus(ADCx) == SET);//在复位完成前会一直在此循环。复位完成后硬件会自动向ADC_CR2置0，本函数返回的即是该寄存器的状态
	ADC_StartCalibration(ADCx);//开始校准
	while(ADC_GetCalibrationStatus(ADCx) == SET);//等待校准完成
}

/**
* @brief  单次触发模式下读取AD值
	* @param  ADCx：选用哪个ADC
	* @param  ADC_FLAG：需要读取的与ADC相关的标志位（参考寄存器ADC_SR的描述）
	* @param  ADC_Channel:指定ADC通道
	* @param  Rank：选择作为规则组的十六个序列中的哪一个（扫描模式下有效，否则就给个1，放在序列1即可）
	* @param  ADC_SampleTime：采样时间。时间越短，采样越快，越不稳定（以上四参数详见ADC_RegularChannelConfig函数）
  * @retval None
  */
uint16_t AD_GetValue_SingleTriggerMode(ADC_TypeDef* ADCx, uint8_t ADC_FLAG, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)
{
	ADC_RegularChannelConfig(ADCx, ADC_Channel, Rank, ADC_SampleTime);//选择规则组的输入通道
	ADC_SoftwareStartConvCmd(ADCx, ENABLE);//单次触发模式下需要不断开启触发转换
	while(ADC_GetFlagStatus(ADCx, ADC_FLAG) == RESET);//等待转换完成
	return ADC_GetConversionValue(ADCx);//读取转换值
}

/**
* @brief  选择连续转换、非扫描模式进行ADC初始化（默认为只开启一路ADC，如有需要增加路数，可使用ADC_AddChannel函数）（对于是否进行中断与看门狗的配置，看函数
					内部注释）连续转换模式的优点是不需要不断触发与等待转换完成，这些可以由硬件自行完成，CPU的压力小一点，如果只有一路ADC，可以使用这种模式，不断读取
					列表中同一位即可
  * @param  RCC_APB2Periph_ADC: 开启哪个ADC的时钟（详见RCC_APB2PeriphClockCmd函数）
	* @param  RCC_APB2Periph_GPIO: 开启哪个GPIO的时钟（同上）
	* @param  GPIOx:选择哪一片GPIO
	* @param  pin：一片GPIO中具体哪个引脚（参照结构体GPIO_InitTypeDef中的定义）
	* @param  RCC_PCLK2：对APB2的72MHz时钟，选择2、4、6、8分频。但因为STM32的ADC最大频率就是14MHz的，给更大的频率会导致稳定性下降，应该选6、8分频（详见
						RCC_ADCCLKConfig函数）
	* @param  ADCx：选用哪个ADC
	* @param  ADC_Channel:指定ADC通道
	* @param  Rank：选择作为规则组的十六个序列中的哪一个（扫描模式下有效，否则就给个1，放在序列1即可）
	* @param  ADC_SampleTime：采样时间。时间越短，采样越快，越不稳定（以上四参数详见ADC_RegularChannelConfig函数）
	* @param  ADC_Mode：工作模式，分独立模式和各种双ADC模式（参考ADC_mode）
	* @param  ADC_DataAlign：对齐方式。STM32的ADC是12位的，而数据寄存器是16位的，因此需要选择数据是向左还是向右对齐。前者会使得数据变大16倍，需要再进
						行处理（参考ADC_data_align）
	* @param  ADC_ExternalTrigConv：外部触发转换选择，即选择硬件触发的触发控制的触发源（如果选用软件触发，这里就填ADC_ExternalTrigConv_None即可，参考
						ADC_external_trigger_sources_for_regular_channels_conversion）
  * @retval None
  */
void AD_Init_ContinuousConvMode(uint32_t RCC_APB2Periph_ADC, uint32_t RCC_APB2Periph_GPIO, GPIO_TypeDef* GPIOx, uint16_t pin, uint32_t RCC_PCLK2, 
	ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime, uint32_t ADC_Mode, uint32_t ADC_DataAlign, uint32_t ADC_ExternalTrigConv)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC, ENABLE);
	gpio_Init(RCC_APB2Periph_GPIO, GPIOx, pin, GPIO_Mode_AIN);
	RCC_ADCCLKConfig(RCC_PCLK2);//配置ADCCLK分频器
	ADC_RegularChannelConfig(ADCx, ADC_Channel, Rank, ADC_SampleTime);//选择规则组的输入通道
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;
	ADC_InitStructure.ADC_NbrOfChannel = 1;//此处为通道数目的选择，默认为1（其实在非扫描模式下序列中只有第一个有效，该成员可以不初始化）
	ADC_Init(ADCx, &ADC_InitStructure);//进行ADC初始化
	/*该部分是中断与看门狗的配置，非必须
	
	*/
	ADC_Cmd(ADCx, ENABLE);//开启ADC电源，以下四个函数进行ADC校准
	ADC_ResetCalibration(ADCx);//复位校准。本质上是软件在ADC_CR2寄存器置1，即SET，于是开始复位
	while(ADC_GetResetCalibrationStatus(ADCx) == SET);//在复位完成前会一直在此循环。复位完成后硬件会自动向ADC_CR2置0，本函数返回的即是该寄存器的状态
	ADC_StartCalibration(ADCx);//开始校准
	while(ADC_GetCalibrationStatus(ADCx) == SET);//等待校准完成
	ADC_SoftwareStartConvCmd(ADCx, ENABLE);//连续转换模式下只需开启一次触发转换即可，会不断刷新寄存器内的值，无需等待转换完成，随时取用即可
}

/**
* @brief  读取AD值（该函数为单次转换模式下才需要用的）
	* @param  ADCx：选用哪个ADC
	* @param  ADC_FLAG：需要读取的与ADC相关的标志位（参考寄存器ADC_SR的描述）
  * @retval None
  */
uint16_t AD_GetValue_ContinuousConvMode(ADC_TypeDef* ADCx, uint8_t ADC_FLAG)
{
	return ADC_GetConversionValue(ADCx);//读取转换值
}

/**
* @brief  选择连续转换模式进行ADC初始化，同时采用循环模式DMA进行数据转运（对于是否进行中断与看门狗的配置，看函数内部注释）连续转换模式的优点是不需要不断触
					发与等待转换完成，这些可以由硬件自行完成，CPU的压力小一点。看函数内部与其余地方隔开的代码的注释，按照需求添加或删除函数
  * @param  RCC_APB2Periph_ADC: 开启哪个ADC的时钟（详见RCC_APB2PeriphClockCmd函数）
	* @param  RCC_APB2Periph_GPIO: 开启哪个GPIO的时钟（同上）
	* @param  GPIOx:选择哪一片GPIO
	* @param  pin：一片GPIO中具体哪个（些）引脚（参照结构体GPIO_InitTypeDef中的定义，如有多个引脚需要初始化，可以将它们或起来，例如
						输入GPIO_Pin_0 | GPIO_Pin_1）
	* @param  RCC_PCLK2：对APB2的72MHz时钟，选择2、4、6、8分频。但因为STM32的ADC最大频率就是14MHz的，给更大的频率会导致稳定性下降，应该选6、8分频（详见
						RCC_ADCCLKConfig函数）
	* @param  ADCx：选用哪个ADC
	* @param  ADC_SampleTime：采样时间。时间越短，采样越快，越不稳定（以上四参数详见ADC_RegularChannelConfig函数）
	* @param  ADC_Mode：工作模式，分独立模式和各种双ADC模式（参考ADC_mode）
	* @param  ADC_DataAlign：对齐方式。STM32的ADC是12位的，而数据寄存器是16位的，因此需要选择数据是向左还是向右对齐。前者会使得数据变大16倍，需要再进
						行处理（参考ADC_data_align）
	* @param  ADC_NbrOfChannel：通道数量。只改这个是没用的，要在函数内部进行增减
	* @param  AD_Value：在主函数内定义的存放AD值的数组，记得传入强制类型转换为uint32_t的数组名
	* @param  DMAy_Channelx：DMA通道。ADC的硬件触发与DMA的通道是一一对应的，如ADC1对应DMA1的通道1，参考DMA1请求映像
  * @retval None
  */
void AD_Init_DMA(uint32_t RCC_APB2Periph_ADC, uint32_t RCC_APB2Periph_GPIO, GPIO_TypeDef* GPIOx, uint16_t pin, uint32_t RCC_PCLK2, ADC_TypeDef* ADCx, 
	uint8_t ADC_SampleTime, uint32_t ADC_Mode, uint32_t ADC_DataAlign, uint8_t ADC_NbrOfChannel, uint32_t AD_Value, DMA_Channel_TypeDef* DMAy_Channelx)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC, ENABLE);
	gpio_Init(RCC_APB2Periph_GPIO, GPIOx, pin, GPIO_Mode_AIN);
	RCC_ADCCLKConfig(RCC_PCLK2);//配置ADCCLK分频器
//	
	ADC_RegularChannelConfig(ADCx, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//选择规则组的输入通道
	ADC_RegularChannelConfig(ADCx, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);//如有需要就再复制粘贴几个该函数，然后第二、三个参数后延即可
//	
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;//使用DMA的场景一定是连续扫描模式
	ADC_InitStructure.ADC_NbrOfChannel = ADC_NbrOfChannel;//此处为通道数目的选择，默认为1（其实在非扫描模式下序列中只有第一个有效，该成员可以不初始化）
	ADC_Init(ADCx, &ADC_InitStructure);//进行ADC初始化
	/*
	该部分是中断与看门狗的配置，非必须
	
	*/
	MyDMA_Init(DMA1_Channel1, (uint32_t)&ADC1->DR, DMA_PeripheralDataSize_HalfWord, DMA_PeripheralInc_Disable, AD_Value, DMA_MemoryDataSize_HalfWord, DMA_MemoryInc_Enable, 
	ADC_NbrOfChannel, DMA_Mode_Circular, DMA_M2M_Disable, DMA_Priority_Medium);//在此处新增入DMA的功能，进行多通道自动转运。AD值的存储寄存器是十六位的，
	//此处选择半字（HalfWord）作为缓冲区大小，外设地址不自增（因为都是从同一个寄存器取值），而存储器地址自增（需要赋值给数组中不同的元素）
	DMA_Cmd(DMAy_Channelx, ENABLE);//手动开启DMA使能。
	ADC_DMACmd(ADCx, ENABLE);//开启DMA触发ADC的信号通道
	ADC_Cmd(ADCx, ENABLE);//开启ADC电源，以下四个函数进行ADC校准
	ADC_ResetCalibration(ADCx);//复位校准。本质上是软件在ADC_CR2寄存器置1，即SET，于是开始复位
	while(ADC_GetResetCalibrationStatus(ADCx) == SET);//在复位完成前会一直在此循环。复位完成后硬件会自动向ADC_CR2置0，本函数返回的即是该寄存器的状态
	ADC_StartCalibration(ADCx);//开始校准
	while(ADC_GetCalibrationStatus(ADCx) == SET);//等待校准完成
	ADC_SoftwareStartConvCmd(ADCx, ENABLE);//连续转换模式下只需开启一次触发转换即可，会不断刷新寄存器内的值，无需等待转换完成，随时取用即可
}
